use crate::error::AppError;
use std::path::Path;
use tokio::fs;
use tokio::io::AsyncWriteExt;
use uuid::Uuid;

pub struct StorageService {
    pub base_path: String,
}

impl StorageService {
    pub fn new(base_path: String) -> Self {
        Self { base_path }
    }

    pub async fn store_file(
        &self,
        original_filename: &str,
        data: &[u8],
    ) -> Result<String, AppError> {
        // Create storage directory if it doesn't exist
        fs::create_dir_all(&self.base_path).await?;

        // Generate unique filename
        let file_extension = Path::new(original_filename)
            .extension()
            .and_then(|ext| ext.to_str())
            .unwrap_or("");

        let unique_filename = format!("{}.{}", Uuid::new_v4(), file_extension);
        let file_path = Path::new(&self.base_path).join(&unique_filename);

        // Write file
        let mut file = fs::File::create(&file_path).await?;
        file.write_all(data).await?;
        file.flush().await?;

        Ok(file_path.to_string_lossy().to_string())
    }

    pub async fn store_file_at_path(
        &self,
        file_path: &str,
        data: &[u8],
    ) -> Result<String, AppError> {
        // Create directory structure if it doesn't exist
        let full_path = Path::new(&self.base_path).join(file_path);
        println!("DEBUG: Storing file at full path: {}", full_path.display());
        if let Some(parent) = full_path.parent() {
            fs::create_dir_all(parent).await?;
        }

        // Write file
        let mut file = fs::File::create(&full_path).await?;
        file.write_all(data).await?;
        file.flush().await?;

        // Return the relative path for database storage
        Ok(file_path.to_string())
    }

    pub async fn get_file(&self, file_path: &str) -> Result<Vec<u8>, AppError> {
        let full_path = if file_path.starts_with(&self.base_path) {
            file_path.to_string()
        } else {
            Path::new(&self.base_path)
                .join(file_path)
                .to_string_lossy()
                .to_string()
        };

        println!("DEBUG: Storage get_file looking for path: {}", full_path);
        let data = fs::read(&full_path).await.map_err(|e| {
            println!("DEBUG: Storage read error for path {}: {}", full_path, e);
            e
        })?;
        Ok(data)
    }

    pub async fn delete_file(&self, file_path: &str) -> Result<(), AppError> {
        if Path::new(file_path).exists() {
            fs::remove_file(file_path).await?;
        }
        Ok(())
    }

    pub async fn file_exists(&self, file_path: &str) -> Result<bool, AppError> {
        Ok(Path::new(file_path).exists())
    }

    pub async fn get_file_size(&self, file_path: &str) -> Result<u64, AppError> {
        let metadata = fs::metadata(file_path).await?;
        Ok(metadata.len())
    }

    pub async fn list_files(&self) -> Result<Vec<String>, AppError> {
        let mut files = Vec::new();
        let mut entries = fs::read_dir(&self.base_path).await?;

        while let Some(entry) = entries.next_entry().await? {
            let path = entry.path();
            if path.is_file() {
                if let Some(path_str) = path.to_str() {
                    files.push(path_str.to_string());
                }
            }
        }

        Ok(files)
    }

    pub async fn cleanup_empty_files(&self) -> Result<usize, AppError> {
        let mut cleaned_count = 0;
        let mut entries = fs::read_dir(&self.base_path).await?;

        while let Some(entry) = entries.next_entry().await? {
            let path = entry.path();
            if path.is_file() {
                let metadata = entry.metadata().await?;
                if metadata.len() == 0 {
                    fs::remove_file(&path).await?;
                    cleaned_count += 1;
                }
            }
        }

        Ok(cleaned_count)
    }

    // DVC-compatible storage methods for unified workflow

    /// Store data file in DVC cache structure (content-addressed storage)
    /// Returns the hash of the stored file
    pub async fn store_data_file(&self, data: &[u8]) -> Result<String, AppError> {
        use crate::utils::hash::calculate_file_hash;

        let hash = calculate_file_hash(data)?;
        let cache_path = format!("{}/cache/{}/{}", self.base_path, &hash[0..2], &hash[2..]);

        // Create directory structure
        if let Some(parent) = Path::new(&cache_path).parent() {
            fs::create_dir_all(parent).await?;
        }

        // Store file if not exists (content-addressed storage)
        if !Path::new(&cache_path).exists() {
            let mut file = fs::File::create(&cache_path).await?;
            file.write_all(data).await?;
            file.flush().await?;
            println!("DEBUG: Stored data file in cache: {}", cache_path);
        } else {
            println!("DEBUG: Data file already exists in cache: {}", cache_path);
        }

        Ok(hash)
    }

    /// Store DVC metadata file in metadata directory
    /// Returns the full path to stored metadata file
    pub async fn store_dvc_metadata(
        &self,
        filename: &str,
        content: &str,
    ) -> Result<String, AppError> {
        let metadata_path = format!("{}/metadata/{}", self.base_path, filename);

        // Create directory structure
        if let Some(parent) = Path::new(&metadata_path).parent() {
            fs::create_dir_all(parent).await?;
        }

        // Store DVC metadata file
        let mut file = fs::File::create(&metadata_path).await?;
        file.write_all(content.as_bytes()).await?;
        file.flush().await?;

        println!("DEBUG: Stored DVC metadata: {}", metadata_path);
        Ok(metadata_path)
    }

    /// Get data file by hash from DVC cache
    pub async fn get_data_file_by_hash(&self, hash: &str) -> Result<Vec<u8>, AppError> {
        let cache_path = format!("{}/cache/{}/{}", self.base_path, &hash[0..2], &hash[2..]);

        println!("DEBUG: Looking for data file by hash: {}", cache_path);

        fs::read(&cache_path).await.map_err(|e| {
            AppError::Internal(format!("Data file not found in cache: {} - {}", hash, e))
        })
    }

    /// Get DVC metadata file
    pub async fn get_dvc_metadata(&self, filename: &str) -> Result<Vec<u8>, AppError> {
        let metadata_path = format!("{}/metadata/{}", self.base_path, filename);

        println!("DEBUG: Looking for DVC metadata: {}", metadata_path);

        fs::read(&metadata_path).await.map_err(|e| {
            AppError::Internal(format!("DVC metadata not found: {} - {}", filename, e))
        })
    }

    /// Check if data file exists in cache by hash
    pub async fn data_file_exists(&self, hash: &str) -> Result<bool, AppError> {
        let cache_path = format!("{}/cache/{}/{}", self.base_path, &hash[0..2], &hash[2..]);
        Ok(Path::new(&cache_path).exists())
    }

    /// Check if DVC metadata file exists
    pub async fn dvc_metadata_exists(&self, filename: &str) -> Result<bool, AppError> {
        let metadata_path = format!("{}/metadata/{}", self.base_path, filename);
        Ok(Path::new(&metadata_path).exists())
    }
}
